/**
 * OSAmI: Open Source Ambient Intelligence
 * A simple publish/subscribe/dispatch API (based on multicast communications) for constrained devices.
 * Copyright (C) Lionel Debroux 2006 (as a TER/Magistere M1 student in the LSR-IMAG-ADELE team, for a demo in the H-Omega project)
 * Copyright (C) Lionel Debroux 2010 (as an engineer in the LCIS-INPG-CTSYS team, for a pre-demo in the FR part of the OSAMI project)
 * Contact: osami-general@osami-commons.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
 */

package fr.imag.adele.homega.impl.unicast;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetSocketAddress;
import java.net.DatagramSocket;
import java.util.logging.Logger;

/**
 * This class listens for packets on a given "topic", and sends them back to the given message handler.
 */
public class UnicastDispatcherThreadImpl
	extends Thread
	implements UnicastDispatcherThread, Runnable {

	/**
	 * Indicates whether the thread object of this instance must terminate itself.
	 */
	private boolean stopThread;
	/**
	 * The controller that creates UnicastDispatcherThreadImpl objects.
	 */
	private UnicastHandler handler;
	/**
	 * The topic handled by this instance.
	 */
	private TopicInfo topic;
	/**
	 * The socket used by this instance.
	 */
	private DatagramSocket socket;
	/**
	 * Indicates whether setting up the socket was fully successful, and therefore,
	 * whether we have to send a group leave message upon termination.
	 */
	private boolean groupJoined;

	public UnicastDispatcherThreadImpl(UnicastHandlerImpl handler, TopicInfo topic) {
		stopThread = false;
		this.handler = handler;
		this.topic = topic;
		this.socket = null;
		groupJoined = false;
	}

	@Override
	public void run() {
		byte[] buf = new byte[1402];
		DatagramPacket p = new DatagramPacket(buf, buf.length);
		InetSocketAddress isa = null;
		groupJoined = false;

		Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("\u001B[1;36mThread dispatching " + topic + " for " + handler + " starting.\u001B[m");

		try {
			// Create multicast socket.
			isa = new InetSocketAddress(topic.getAddress(),topic.getPort());
			
			socket = new DatagramSocket(topic.getPort());

			//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Dispatcher thread: multicast socket created on interface !" + socket.getInterface());

			//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Dispatcher thread: group " + isa + " joined !");
		}
		catch (Exception e) {
			// An exception occurred, this thread must terminate !
			e.printStackTrace();
			try {
				throw e;
			} catch (Exception e1) {
				e1.printStackTrace();
			}
			stopThread = true;
		}
		
		//notifyAll();
		
		// Thread loop...
		while (!stopThread) {
			// Wait for packets... 
			try {
				p.setLength(1401);
				socket.receive(p);
				
				for (int i = 0; i < 1401; i++) {
					if ((p.getData())[i] == 0) {
						p.setLength(i+1);
						break;
					}
				}
				
				//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Dispatcher thread " + topic + ": packet received !");
				
				/*System.out.println("RECEIVED DATA:");
				for (int i = 0; i < 1401; i++) {
					if (i % 16 == 0) {
						System.out.println();
					}
					System.out.printf("%02X ",(p.getData())[i]);
					if ((p.getData())[i] == 0) {
						System.out.printf("\nLength: %04X\n",i);
						p.setLength(i);
						break;
					}
				}
				System.out.println();*/

				// Dispatch them.
				handler.dispatch(socket, p);
			}
			catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
		
		leave();
	}

	@Override
	public void terminate() {
		stopThread = true;
		leave();
		Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("\u001B[1;36mThread dispatching " + topic + " for " + handler + " terminated.\u001B[m");
	}

	private void leave() {
		if ((socket != null) && !socket.isClosed()) {
			socket.close();
		}
	}

	/**
	 * Bug catcher...
	 */
	@Override
	public void finalize() {
		if (isAlive()) {
			System.out.println("\u001B[1;5;31mForgot to terminate the thread dispatching " + topic + " for " + handler + " !\u001B[m");
			terminate();
		}
	}
}
